home *** CD-ROM | disk | FTP | other *** search
- /* @(#)src/bindlib.c 1.16 9/5/92 22:16:42 */
-
- /*
- * Copyright (C) 1992 Ronald S. Karr
- *
- * See the file COPYING, distributed with smail, for restriction
- * and warranty information.
- */
-
- /*
- * bindlib.c
- * common code for the BIND router and the "tcpsmtp" transport,
- * both of which can the Domain Name Service as implemented
- * (typically) by the a Berkeley Internet Name Domain (BIND)
- * server.
- *
- * bind code converted to general library by Chip Salzenberg.
- */
-
- /*
- * Specifications for the bind router and/or tcpsmtp transport:
- *
- * associated transports:
- * Generally used with an smtp transport.
- *
- * private attribute data:
- * ignore_domains: domains to ignore. Names under any of these
- * domains will never be matched. This prevents expensive
- * lookups for domains that are known not to be in the DNS.
- *
- * private attribute flags:
- * defer_no_connect: if set and we cannot connect to the
- * name server, try again later. This is set by default.
- * local_mx_okay: if not set, an MX record which points to the
- * local host is considered to be an error which will
- * will cause mail to be returned to the sender.
- * defnames: append a default domain to an unqualified hostname,
- * using the RES_DEFNAME flag to the resolver library.
- * This is set by default.
- * domain_required: at least two name components are required
- * in the hostname. Setting this prevents lookups of
- * single-component names which are unlikely to be valid
- * hosts in the DNS.
- * mx_only: use only MX records in matching an address. If a
- * host doesn't have a MX records, then don't use the A
- * or WKS records for routing.
- *
- * algorithm:
- * For bind routing, given a target use the following
- * strategy:
- *
- * 1. Replace sequences of more than one dot in the target
- * with a single dot. Remove any dots from the beginning
- * and end of the target.
- *
- * 2. See if there is an MX record for the target. If so,
- * skip to step 6.
- *
- * 3. If the MX record query returned a CNAME record, then
- * treat the canonical name as the target and return to
- * step 2.
- *
- * 4. Do not match the target.
- *
- * 5. If the primary name of the local host is listed in an
- * MX record, all MX record's with equal or greater
- * preference fields are discarded.
- *
- * 6. If the only MX record remaining references the local
- * host, do not match the target.
- *
- * 7. Choose one of the MX records with the lowest
- * preference fields. Match the address with the
- * next_host value set to the referenced host.
- *
- * The algorithm is extended for the UK domain by (1)
- * checking a list of top-level domains and converting the
- * target to a gateway name if matched, (2) discarding any MX
- * records which match the uk_ignore_gateways parameter, or
- * whose preference value is greater than uk_max_precedence,
- * (3) re-trying with the list of widening strings and then
- * trying the address inverted before giving up, and (4)
- * matching with the greybook transport if if MX records were
- * found but were all discarded by the above rules.
- *
- * When a hostname is matched, the next_host field is set to
- * the referenced host, for MX records, or the matched host
- * for A records. The route field is set to the canonical
- * name for the host, if that is different from the original
- * target host. The match length is always set to the length
- * of the original target host.
- *
- * NOTE: Use of WKS records is enabled if the USE_WKS_RECORDS macro
- * is defined. This can be set from the EDITME file with the
- * MISC_H_DEFINES variable. If this macro is not defined, then
- * WKS records are not retrieved and are assumed to exist and
- * to contain the SMTP service.
- */
-
- #define NEED_SOCKETS
- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <sys/param.h>
- #include "defs.h"
-
- /*
- * Compilation of this entire file depends on "HAVE_BIND".
- */
-
- #ifdef HAVE_BIND
-
- #include "smail.h"
- #include "addr.h"
- #include "route.h"
- #include "transport.h"
- #include "bindlib.h"
- #include "bindsmtpth.h"
- #include "lookup.h"
- #include "dys.h"
- #ifndef DEPEND
- # include "extern.h"
- # include "debug.h"
- # include "error.h"
- #endif
-
- #if PACKETSZ > 1024
- # define MAXPACKET PACKETSZ
- #else
- # define MAXPACKET 1024
- #endif
-
- #ifndef GETSHORT
- /*
- * earlier versions of bind don't seem to define these useful macros,
- * so roll our own.
- */
- # define GETSHORT(i, p) \
- ((i) = ((unsigned)(*(p)++ & 0xff) << 8), \
- (i) |= ((unsigned)(*(p)++ & 0xff)))
- # define GETLONG(l, p) \
- ((l) = ((unsigned long)(*(p)++ & 0xff) << 24), \
- (l) |= ((unsigned long)(*(p)++ & 0xff) << 16), \
- (l) |= ((unsigned long)(*(p)++ & 0xff) << 8), \
- (l) |= ((unsigned long)(*(p)++ & 0xff)))
- # define PUTSHORT(i, p) \
- ((*(p)++ = (unsigned)(i) >> 8), \
- (*(p)++ = (unsigned)(i)))
- # define PUTLONG(l, p) \
- ((*(p)++ = (unsigned long)(l) >> 24), \
- (*(p)++ = (unsigned long)(l) >> 16), \
- (*(p)++ = (unsigned long)(l) >> 8), \
- (*(p)++ = (unsigned long)(l)))
- #endif
-
- /*
- * The standard rrec structure doesn't have a space for the domain
- * name, so define our own.
- */
- enum rr_sect { SECT_AN, SECT_NS, SECT_AR };
- typedef struct rr {
- enum rr_sect rr_sect; /* resource record section */
- char *rr_dname; /* domain name */
- short rr_class; /* class number */
- short rr_type; /* type number */
- int rr_size; /* size of data area */
- char *rr_data; /* pointer to data */
- } RR;
-
- /* structure for iterating over RR's with getnextrr() */
- struct rr_iterator {
- RR rr; /* space for storing RR */
- char dname[MAXDNAME]; /* space for storing domain name */
- char *dp; /* pointer within packet */
- HEADER *hp; /* saved header pointer */
- char *eom; /* end of packet */
- int ancount; /* count of answer records */
- int nscount; /* count of ns records */
- int arcount; /* count of additional records */
- };
-
- /*
- * import h_errno; many systems don't define it in <netdb.h>
- */
-
- #ifndef OBSOLETE_RESOLVER
- extern int h_errno;
- #endif
-
- /* functions local to this file */
-
- #ifdef ANSI_C
- # define P_(x) x
- #else
- # define P_(x) ()
- #endif
-
- static int bind_addr_work P_((char*,long,struct bindlib_private*,char*,
- struct rt_info*,struct error **));
- static char *strip_dots P_((char*));
- static void flip P_((char *)); /* Flip address between UK/world order */
- static char *rewrite_header P_((char *, char *, char *));
- static int get_records P_((char*,int,HEADER*,int*,char **));
- static RR *getnextrr P_((struct rr_iterator*,int));
- static void rewindrr P_((struct rr_iterator*,HEADER*,int));
- static int find_a_records P_((struct mx_transport_hint*,char*,char **));
- static struct error *lost_server P_((char*));
- static struct error *server_failure P_((char*,char*));
- static struct error *packet_error P_((char*,char*,char*));
- static struct error *no_valid_mx_records P_((char*,char*));
- static struct error *matched_local_host P_((char*,char*));
- static struct error *no_transport_error P_((char*,char*));
- static int decode_mx_rr P_((RR*,struct rr_iterator*,char **,int*));
- static struct transport_hints * new_mx_hint P_((int,char*,int));
- static void add_mx_hint P_((struct transport_hints **,int,char*,int));
- static void free_mx_hint P_((struct transport_hints **));
- static void add_a_hint P_((struct mx_transport_hint*,char*,char*));
-
- /*
- * bind_addr - lookup a host through the domain system
- *
- * Use the algorithm described at the top of this source file for
- * finding a match for a target.
- *
- * Return one of the following values:
- *
- * These return codes apply only to the specific address:
- * DB_SUCCEED Matched the target host.
- * DB_NOMATCH Did not match the target host.
- * DB_FAIL Fail the address with the given error.
- * DB_AGAIN Try to route with this address again at a
- * later time.
- *
- * These return codes apply to this router in general:
- * FILE_NOMATCH There is no server running on this machine.
- * FILE_AGAIN Lost contact with server, or server is
- * required to exist. Try again later.
- * FILE_FAIL A major error has been caught in router,
- * notify postmaster.
- */
-
- int
- bind_addr(raw_target, flags, priv, what, rt_info, error_p)
- char *raw_target; /* raw target address */
- long flags; /* bind-specific flags */
- struct bindlib_private *priv; /* bind-specific data */
- char *what; /* who called, for messages */
- struct rt_info *rt_info; /* return route info here */
- struct error **error_p; /* return lookup error here */
- {
- long save_res_options; /* like _res.options in <resolv.h> */
- int ret;
-
- save_res_options = _res.options;
- ret = bind_addr_work(raw_target, flags, priv, what, rt_info, error_p);
- _res.options = save_res_options;
-
- return ret;
- }
-
- static int
- bind_addr_work(raw_target, flags, priv, what, rt_info, error_p)
- char *raw_target; /* raw target address */
- long flags; /* bind-specific flags */
- struct bindlib_private *priv; /* bind-specific data */
- char *what; /* who called, for messages */
- struct rt_info *rt_info; /* return route info here */
- struct error **error_p; /* return lookup error here */
- {
- char *target; /* raw_target stripped of extra dots */
- static char *orig_target = NULL; /* original dot-stripped target */
- static char *full_target = NULL; /* target sent by res_send */
- HEADER *mx_rrs = NULL; /* response for MX rr's */
- int mx_size; /* size of MX rr response packet */
- struct rr_iterator mx_it; /* MX rr iterator */
- struct rr_iterator a_it; /* A rr iterator */
- RR *mx_rr; /* a single MX rr */
- RR *a_rr; /* a single A rr */
- static int no_server = FALSE; /* TRUE if no server process */
- static int found_server = FALSE; /* TRUE if server process found */
- int success; /* value to return */
- char *error; /* error text from called function */
- int widencount = 0; /* number of times widened */
- int inverting = FALSE; /* trying inverted address */
- int gated = FALSE; /* routed via a gateway */
- int UK_MX_rejected = FALSE; /* at least one MX was rejected */
- int local_precedence = -1; /* precedence of MX to local host */
- struct transport_hints *mx_hints = 0, **mx_a;
- struct transport_hints *hint;
-
- /*
- * memory management notes:
- * target points to static area returned by strip_dots().
- * do not free.
- * orig_target static pointer of convenience. do not free.
- * full_target static pointer of convenience. do not free.
- */
-
- if (no_server) {
- return DB_NOMATCH;
- }
-
- /*
- * strip extra dots from the target to ensure sane lookups.
- * If the target contained any dots before being stripped,
- * then don't allow the resolver to add the default domain.
- * This allows mail to high-level domains (e.g., "com.")
- * while also allowing use of default domain suffixes.
- */
-
- #ifdef RES_DEFNAMES
- # ifndef RES_DNSRCH
- # define RES_DNSRCH 0
- # endif
- if (flags & BIND_DEFNAMES && strchr(raw_target, '.') == NULL) {
- _res.options |= RES_DEFNAMES|RES_DNSRCH;
- } else {
- _res.options &= ~(RES_DEFNAMES|RES_DNSRCH);
- }
- #endif
-
- /*
- * Step 1: Omit needless dots!
- */
-
- target = strip_dots(raw_target);
-
- if (orig_target) {
- xfree(orig_target);
- }
- orig_target = COPY_STRING(target);
-
- if (full_target) {
- xfree(full_target);
- }
- full_target = NULL;
-
- /*
- * if the target is in one of the "ignore" domains, then don't
- * match it. This prevents expensive lookups of domains that are
- * known not to exist in the DNS, such as .uucp or .bitnet.
- */
-
- if (priv->ignore_domains) {
- if (match_end_domain(priv->ignore_domains, target) != NULL) {
- return DB_NOMATCH;
- }
- }
-
- if (debug >= DBG_DRIVER_HI) {
- _res.options |= RES_DEBUG; /* turn on resolver debugging */
- }
-
- /*
- * first see if the address matches one of the list of special
- * gateways. if so, change the target to the gateway, and set
- * the full_target to the original string so it will be passed
- * on to the gateway. the flag prevents subsequent updating of
- * full_target from the DNS records.
- */
-
- if (priv->gateways) {
- char *cur;
- char *gateway = NULL;
-
- for (cur = strcolon(priv->gateways);
- cur && !gated;
- cur = strcolon((char *)NULL))
- {
- if (!gateway) {
- gateway = COPY_STRING(cur);
- }
- else if (EQ(cur, "+")) {
- if (gateway) {
- xfree(gateway);
- }
- gateway = NULL;
- }
- else if (is_suffix(cur, target, FALSE)) {
- full_target = COPY_STRING(target);
- target = strip_dots(gateway);
- gated = TRUE;
- }
- }
-
- if (gateway) {
- xfree(gateway);
- }
- }
-
- /* store return values for common case */
- rt_info->next_host = COPY_STRING(target);
- rt_info->route = NULL;
- rt_info->matchlen = strlen(raw_target);
-
- /*
- * if the domain_required flag is set, then the target hostname
- * is required to have at least one "."
- */
-
- if (flags & BIND_DOMAIN_REQUIRED && strchr(target, '.') == NULL) {
- return DB_NOMATCH;
- }
-
- get_target_mx_records:
-
- DEBUG1(DBG_ROUTE_LO, "Trying %s\n", target);
-
- /* Step 2: See if there are any MX records */
- if (mx_rrs == NULL) {
- mx_rrs = (HEADER *)xmalloc(MAXPACKET);
- }
-
- success = get_records(target, T_MX, mx_rrs, &mx_size, &error);
- if ((success == DB_NOMATCH) && !(flags & BIND_MX_ONLY))
- success = get_records(target, T_ANY, mx_rrs, &mx_size, &error);
-
- if (success != DB_SUCCEED) {
- switch (success) {
-
- case FILE_NOMATCH:
- if (! (flags & BIND_DEFER_NO_CONN) && ! found_server) {
- no_server = TRUE;
- success = DB_NOMATCH;
- break;
- }
- if (found_server) {
- *error_p = lost_server(what);
- } else {
- *error_p =
- server_failure(what, "Connection to BIND server failed");
- }
- success = FILE_AGAIN;
- break;
-
- case DB_AGAIN:
- case DB_FAIL:
- case FILE_AGAIN:
- *error_p = server_failure(what, error);
- break;
-
- case DB_NOMATCH:
-
- /*
- * Before giving up, we try widening the name according to a
- * configured list. Because the strcolon routine re-uses its
- * store, we can't remember the position globally. We just
- * remember the count of widenings. As there will typically be
- * only two or three, don't worry about the inefficiency.
- */
-
- /* Don't do it if gated. (Shouldn't get here, anyway.) */
- if (!gated) {
- char *w;
- int i;
-
- w = strcolon(priv->widen_domains ? priv->widen_domains : "");
- for (i = 0; i < widencount; i++)
- w = strcolon((char *)NULL);
- if (w && *w)
- {
- struct str new; /* region for building new string */
-
- STR_INIT(&new);
- STR_CAT(&new, orig_target);
- STR_NEXT(&new, '.');
- STR_CAT(&new, w);
- STR_NEXT(&new, '\0');
- STR_DONE(&new);
-
- target = strip_dots(new.p);
- xfree(rt_info->next_host);
- rt_info->next_host = new.p;
-
- widencount++;
- goto get_target_mx_records;
- }
-
- /*
- * Deal with setting up for flipping addresses; don't do it
- * the address is already in the "uk" domain.
- */
-
- else if ((flags & BIND_UK_TRY_INVERT) && !inverting &&
- !is_suffix("uk", orig_target, TRUE))
- {
- inverting = TRUE;
- flip(orig_target); /* does it in situ */
-
- target = strip_dots(orig_target);
- xfree(rt_info->next_host);
- rt_info->next_host = COPY_STRING(target);
-
- widencount = 0;
- goto get_target_mx_records;
- }
- }
- break;
- }
-
- xfree((char *)mx_rrs);
- return success;
- }
-
- /* We have connected to the server at least once */
- found_server = TRUE;
-
- /* See if there are any MX records */
- rewindrr(&mx_it, mx_rrs, mx_size);
- while ((mx_rr = getnextrr(&mx_it, FALSE)) && mx_rr->rr_type != T_MX)
- ;
-
- if (mx_rr == NULL) {
-
- /* Step 3: Look for a CNAME record */
- rewindrr(&mx_it, mx_rrs, mx_size);
- while ((mx_rr = getnextrr(&mx_it, FALSE)) && mx_rr->rr_type != T_CNAME)
- ;
- if (mx_rr) {
- static char nambuf[MAXDNAME];
- int dlen;
-
- dlen = dn_expand((char *)mx_it.hp, mx_it.eom, mx_rr->rr_data,
- nambuf, MAXDNAME);
- if (dlen < 0) {
- /* format error in response packet */
- *error_p = packet_error(what, "CNAME", target);
-
- xfree((char *)mx_rrs);
- return DB_AGAIN;
- }
- target = nambuf;
- xfree(rt_info->next_host);
- rt_info->next_host = COPY_STRING(target);
-
- /*
- * RFC 974:
- * There is one other special case. If the response contains
- * an answer which is a CNAME RR, it indicates that REMOTE is
- * actually an alias for some other domain name. The query
- * should be repeated with the canonical domain name.
- */
- /*
- * The next query should really be done with the DEFNAMES flag
- * cleared !!
- */
- goto get_target_mx_records;
- }
-
- if (! (flags & BIND_MX_ONLY)) {
- /*
- * from RFC 974:
- * It is possible that the list of MXs in the response to
- * the query will be empty. This is a special case. If the
- * list is empty, mailers should treat it as if it contained
- * one RR, an MX RR with a preference value of 0, and a host
- * name of REMOTE. (I.e., REMOTE is its only MX). In
- * addition, the mailer should do no further processing on
- * the list, but should attempt to deliver the message to
- * REMOTE. The idea here is that if a domain fails to
- * advertise any information about a particular name we will
- * give it the benefit of the doubt and attempt delivery.
- */
- char *fullname;
-
- if (islocalhost(target))
- local_precedence = 0;
- if (_res.options & RES_DEFNAMES &&
- strchr(target, '.') == NULL &&
- _res.defdname[0] != '\0')
- {
- add_mx_hint(&mx_hints, 0,
- xprintf("%s.%s", target, _res.defdname),
- TRUE);
- fullname = xprintf("%s.%s", target, _res.defdname);
- } else {
- add_mx_hint(&mx_hints, 0, target, TRUE);
- fullname = COPY_STRING(target);
- }
-
- /*
- * Deal with widening of the target name. This may have been
- * done explicitly above, or it may have been done by the DNS
- * resolver for an unqualified name. We cautiously do this only
- * for UK addresses, but it is probably OK in general, except
- * that it wastes time. Note: do *not* do this for explicitly
- * gatewayed addresses. We have to do this separately for the
- * MX and non-MX cases, as the data comes from different places,
- * in the MX case, aliases may have been followed, etc.
- */
-
- if (!gated && is_suffix("uk", fullname, TRUE)) {
- struct list *q;
-
- for (q = header; q; q = q->succ) {
- if (strncmpic(q->text, "to:", 3) == 0 ||
- strncmpic(q->text, "cc:", 3) == 0)
- {
- q->text = rewrite_header(q->text, raw_target,
- fullname);
- }
- }
- }
-
- xfree(fullname);
- }
- }
-
- /* mx records received: scan them */
-
- else {
- rewindrr(&mx_it, mx_rrs, mx_size);
- while (mx_rr = getnextrr(&mx_it, FALSE)) {
- if (mx_rr->rr_type == T_MX) {
- char *name;
- int precedence;
-
- success = decode_mx_rr(mx_rr, &mx_it, &name, &precedence);
- if (success != DB_SUCCEED) {
- *error_p = packet_error(what, "MX", target);
- xfree((char *)mx_rrs);
- return DB_AGAIN;
- }
- /*
- * grab the domain returned in the packet as
- * the real target name, unless gatewayed
- */
- if (!gated) {
- if (full_target) {
- xfree(full_target);
- }
- if (! EQIC(target, mx_it.dname)) {
- full_target = COPY_STRING(mx_it.dname);
- } else {
- full_target = COPY_STRING(target);
- }
- }
-
- /*
- * Pick up the local host's precedence.
- */
- if (islocalhost(name)
- && (local_precedence < 0 || precedence < local_precedence))
- {
- local_precedence = precedence;
- }
-
- /*
- * If the address is in the UK, and is not gated, we must
- * modify the To: and Cc: fields with the widened address.
- */
-
- if (!gated && is_suffix("uk", mx_it.dname, TRUE))
- {
- struct list *q;
-
- for (q = header; q; q = q->succ) {
- if (strncmpic(q->text, "to:", 3) == 0 ||
- strncmpic(q->text, "cc:", 3) == 0)
- {
- q->text = rewrite_header(q->text, raw_target,
- mx_it.dname);
- }
- }
- }
-
- /*
- * Use an MX if precedence is within bounds and if
- * it doesn't specify a gateway to be ignored.
- */
-
- if (is_suffix("uk", mx_it.dname, TRUE) &&
- ((priv->uk_max_precedence > 0 &&
- precedence > priv->uk_max_precedence) ||
- (priv->uk_ignore_gateways &&
- is_string_in_list(name, priv->uk_ignore_gateways))))
- {
- UK_MX_rejected = TRUE;
- }
- else
- {
- /*
- * This test sometimes avoids adding exchangers that must
- * not be used.
- */
- if (local_precedence < 0 || precedence < local_precedence)
- add_mx_hint(&mx_hints, precedence, name, FALSE);
- }
- }
- }
- }
-
- mx_a = &mx_hints;
- while (*mx_a) {
- #define mx_hint ((struct mx_transport_hint *)((*mx_a)->private))
- int precedence = mx_hint->preference;
-
- if (local_precedence >= 0 && precedence >= local_precedence) {
- /*
- * RFC 974:
- * If the domain name LOCAL is listed as an MX RR, all MX
- * RRs with a preference value greater than or equal to that
- * of LOCAL's must be discarded.
- */
- free_mx_hint(mx_a);
- continue;
- }
- mx_a = &(*mx_a)->succ;
- #undef mx_hint
- }
-
- /* if there were no valid MX records... */
- if (! mx_hints) {
- xfree((char *)mx_rrs);
-
- if (local_precedence >= 0) {
- if ((flags & BIND_LOCAL_MX_OKAY) == 0) {
- *error_p = matched_local_host(what, target);
- return DB_FAIL;
- }
- return DB_NOMATCH;
- }
-
- /*
- * This is not an error in the UK if MX records were rejected
- * for "UK" reasons (i.e. via the preference value or
- * uk_ignore_gateways.) In this case, we assume that JANET mail
- * can reach the address, since all sites with this kind of
- * gateway MX record should be accessible via GreyBook mail.
- *
- * A configuration option specifies whether this system has a
- * greybook transport or not. If not, just yield NOMATCH,
- * assuming a smarthost driver will pick up the address.
- */
-
- if (UK_MX_rejected)
- {
- struct transport *tp;
-
- if (priv->uk_greybook_transport == NULL)
- return DB_NOMATCH;
-
- rt_info->transport = find_transport(priv->uk_greybook_transport);
- rt_info->route = full_target;
- full_target = NULL;
-
- if (!rt_info->transport) {
- *error_p = no_transport_error(what,
- priv->uk_greybook_transport);
- return DB_NOMATCH;
- }
-
- /*
- * Ensure that the target and the route are in UK order,
- * unless the option is set to suppress this (future hope!)
- */
- if (!(flags & BIND_UK_GREY_WORLD))
- {
- if (is_suffix("uk", rt_info->route, TRUE))
- flip(rt_info->route);
- if (is_suffix("uk", rt_info->next_host, TRUE))
- flip(rt_info->next_host);
- }
- return DB_SUCCEED;
- }
-
- /* it was an error, after all */
- if (!(flags & BIND_MX_ONLY)) {
- *error_p = no_valid_mx_records(what, target);
- }
- return DB_NOMATCH;
- }
-
- /*
- * if any MX record points to a different host than the target,
- * make sure the target hostname is passed to that host.
- */
- if (full_target) {
- for (hint = mx_hints; hint; hint = hint->succ) {
- if (EQIC(hint->hint_name, "mx")) {
- #define mx_hint ((struct mx_transport_hint *)(hint->private))
- if (! EQIC(mx_hint->exchanger, full_target)) {
- rt_info->route = full_target;
- full_target = NULL;
- break;
- }
- }
- }
- }
-
- /* look for relevant A records in the additional section of the MX answer */
- rewindrr(&a_it, mx_rrs, mx_size);
- while ((a_rr = getnextrr(&a_it, TRUE))!=0) {
- if (a_rr->rr_type==T_A) {
- for (hint = mx_hints; hint; hint = hint->succ) {
- #define mx_hint ((struct mx_transport_hint *)(hint->private))
- if (EQIC(mx_hint->exchanger, a_it.dname))
- add_a_hint(mx_hint, a_it.dname, a_rr->rr_data);
- #undef mx_hint
- }
- }
- }
-
- xfree((char *)mx_rrs);
- mx_a = &mx_hints;
- while (*mx_a) {
- #define mx_hint ((struct mx_transport_hint *)((*mx_a)->private))
- if (! mx_hint->ipaddrs) {
- /* Go out and get an A record */
- success = find_a_records(mx_hint, mx_hint->exchanger, &error);
- switch (success) {
-
- case DB_SUCCEED:
- mx_a = &(*mx_a)->succ;
- break;
-
- case DB_NOMATCH:
- if (! mx_hint->implicit)
- /* This is an error! */;
- free_mx_hint(mx_a);
- break;
-
- case FILE_NOMATCH:
- success = FILE_AGAIN;
- /* FALL THROUGH */
-
- case DB_FAIL:
- case DB_AGAIN:
- case FILE_AGAIN:
- /* Step 5: Do not match the target */
- *error_p = server_failure(what, error);
- return success;
- }
- } else {
- mx_a = &(*mx_a)->succ;
- }
- #undef mx_hint
- }
-
- if (mx_hints) {
- rt_info->tphint_list = mx_hints;
- return DB_SUCCEED;
- } else {
- return DB_NOMATCH;
- }
- }
-
- /*
- * strip_dots - remove extra dots from a hostname string
- *
- * Remove all dots from the beginning and end of a string. Also, any
- * sequence of more than one dot is replaced by a single dot. For
- * example, the string:
- *
- * .att..com.
- *
- * will result in the string:
- *
- * att.com
- *
- * The operation is non-destructive on the passed string. The
- * resulting value points to a region which may be reused on
- * subsequent calls to strip_dots().
- */
- static char *
- strip_dots(s)
- register char *s;
- {
- static struct str new; /* region for building new string */
- static int inited = FALSE; /* true if target initialized */
-
- /* initialize or clear the new string */
- if (! inited) {
- STR_INIT(&new);
- } else {
- new.i = 0;
- }
-
- /*
- * copy target, removing extra dots.
- */
- while (*s == '.') s++;
- do {
- if (*s == '.') {
- while (*s == '.') s++;
- if (*s) --s;
- }
- STR_NEXT(&new, *s);
- } while (*s++);
-
- return new.p;
- }
-
- /*
- * flip - flip address between UK/world order
- *
- * operates on the string in situ
- */
- static void
- flip(s)
- char *s;
- {
- static struct str new; /* region for building new string */
- static int inited = FALSE; /* true if target initialized */
- char *p;
-
- /* initialize or clear the new string */
- if (! inited) {
- STR_INIT(&new);
- } else {
- new.i = 0;
- }
-
- p = s + strlen(s);
- while (p > s) {
- if (*--p == '.') {
- *p = '\0';
- if (*(p + 1)) {
- STR_CAT(&new, p + 1);
- if (p > s) {
- STR_NEXT(&new, '.');
- }
- }
- }
- }
- STR_CAT(&new, s);
- strcpy(s, new.p);
- }
-
- /*
- * rewrite-header - re-write an address in a header to be the
- * correct UK address, possibly widened and/or re-ordered from
- * what the user typed. Used for To: and Cc: fields.
- *
- * Under some circumstances smail seems to go round the loop
- * twice, so be careful not to do the job twice.
- */
-
- static char *
- rewrite_header(text, oldtarget, newtarget)
- char *text;
- char *oldtarget;
- char *newtarget;
- {
- char *p = text;
- int oldlen = strlen(oldtarget);
- int newlen = strlen(newtarget);
-
- while (*p)
- {
- while (*p != 0 && *p != '@')
- p++;
- if (*p == 0)
- break;
-
- if (strncmpic(++p, oldtarget, oldlen) == 0)
- {
- int c = p[oldlen];
- if (c == 0 || c == ',' || c == '>' ||
- c == ' ' || c == '\n' || c == '\t')
- {
- if (oldlen == newlen)
- memcpy(p, newtarget, newlen);
- else
- {
- int baselen = p - text;
- int oldtextlen = strlen(text);
- char *newtext = xmalloc(oldtextlen+1+(newlen-oldlen));
- memcpy(newtext, text, baselen);
- memcpy(newtext+baselen, newtarget, newlen);
- memcpy(newtext+baselen+newlen, p+oldlen,
- oldtextlen-baselen-oldlen+1);
- xfree(text);
- text = newtext;
- p = text + baselen + newlen;
- }
- }
- }
- }
-
- return text;
- }
-
-
- /*
- * get_records - query the domain system for resource records of the given
- * name.
- *
- * Send out a query and return the response packet in a passed buffer.
- * Resource records are in the bytes following the header for that
- * packet. The actual size of the response packet is stored in pack_size.
- *
- * The passed answer buffer must have space for at least MAXPACKET
- * bytes of data.
- *
- * Return one of the following response codes:
- *
- * DB_SUCCEED We received an affirmative packet from the
- * server.
- * DB_NOMATCH We received a negative response from the server
- * indicating that the name does not exist.
- * DB_FAIL We received a negative response from the
- * server indicating some problem with the
- * packet.
- * DB_AGAIN We received a negative response from the
- * server indicating a temporary failure while
- * processing the name.
- * FILE_NOMATCH We could not connect to the server.
- * FILE_AGAIN There was a failure in the server, try again
- * later.
- */
- static int
- get_records(qname, qtype, answer, pack_size, error)
- char *qname; /* search for this name */
- int qtype; /* and for records of this type */
- register HEADER *answer; /* buffer for storing answer */
- int *pack_size; /* store answer packet size here */
- char **error; /* store error message here */
- {
- #ifdef OBSOLETE_RESOLVER
- char msgbuf[MAXPACKET];
- #endif
- int msglen;
- int anslen;
-
- #ifdef OBSOLETE_RESOLVER
- msglen = res_mkquery(QUERY, qname, C_IN, qtype, (char *)NULL, 0,
- (struct rrec *)NULL, msgbuf, MAXPACKET);
-
- anslen = res_send(msgbuf, msglen, (char *)answer, MAXPACKET);
- if (anslen < 0) {
- return FILE_NOMATCH;
- }
- #else /* not OBSOLETE_RESOLVER */
- anslen = res_search(qname, C_IN, qtype, answer, MAXPACKET);
- if (anslen < 0) {
- switch (h_errno) {
- case NO_DATA:
- *pack_size = 0;
- return DB_SUCCEED;
-
- case HOST_NOT_FOUND:
- return DB_NOMATCH;
-
- case TRY_AGAIN:
- *error = "Nameserver: Server failure";
- return DB_AGAIN;
-
- case NO_RECOVERY:
- *error = "Irrecoverable nameserver error";
- return FILE_NOMATCH;
-
- default:
- *error = "Unknown nameserver error";
- return FILE_NOMATCH;
- }
- }
- #endif /* not OBSOLETE_RESOLVER */
- *pack_size = anslen;
-
- answer->qdcount = ntohs(answer->qdcount);
- answer->ancount = ntohs(answer->ancount);
- answer->nscount = ntohs(answer->nscount);
- answer->arcount = ntohs(answer->arcount);
-
- switch (answer->rcode) {
-
- case NOERROR:
- return DB_SUCCEED;
- #ifdef OBSOLETE_RESOLVER
- case FORMERR:
- *error = "Nameserver: Format error in packet";
- return DB_FAIL;
-
- case SERVFAIL:
- *error = "Nameserver: Server failure";
- return DB_AGAIN;
-
- case NXDOMAIN:
- return DB_NOMATCH;
-
- case NOTIMP:
- *error = "Nameserver: Unimplemented request";
- return DB_FAIL;
-
- case REFUSED:
- *error = "Nameserver: Query refused";
- return FILE_AGAIN;
- #endif /* OBSOLETE_RESOLVER */
- default:
- *error = "Nameserver: Unknown response code";
- return DB_FAIL;
- }
- }
-
- /*
- * getnextrr - get a sequence of resource records from a name server
- * response packet.
- *
- * The first time getnextrr() is called to process a packet, pass it
- * the header address of the packet. For subsequent calls pass NULL.
- * When no more records remain, getnexrr() returns NULL.
- *
- * To process a specific response section, pass the section in sect.
- */
- static RR *
- getnextrr(it, additional)
- register struct rr_iterator *it; /* iteration variables */
- int additional; /* interested in additional section RRs */
- {
- int dnamelen;
- register unsigned char *dp;
-
- dp = (unsigned char *)it->dp;
-
- /* return NULL if no rr's remain */
- if (it->ancount != 0) {
- --it->ancount;
- it->rr.rr_sect = SECT_AN;
- } else if (additional && it->nscount != 0) {
- --it->nscount;
- it->rr.rr_sect = SECT_NS;
- } else if (additional && it->arcount != 0) {
- --it->arcount;
- it->rr.rr_sect = SECT_AR;
- } else {
- return NULL;
- }
-
- dnamelen = dn_expand((char *)it->hp, it->eom, dp, it->dname, MAXDNAME);
- if (dnamelen < 0) {
- return NULL;
- }
- dp += dnamelen;
- GETSHORT(it->rr.rr_type, dp); /* extract type from record */
- GETSHORT(it->rr.rr_class, dp); /* extract class */
- dp += 4; /* skip time to live */
- GETSHORT(it->rr.rr_size, dp); /* extract length of data */
- it->rr.rr_data = (char *)dp; /* there is the data */
- it->dp = (char *)(dp + it->rr.rr_size); /* skip to next resource record */
-
- return &it->rr;
- }
-
- static void
- rewindrr(it, hp, packsize)
- register struct rr_iterator *it;
- HEADER *hp;
- int packsize;
- {
- int dnamelen;
- int qdcount;
-
- it->dp = (char *)(hp + 1);
- it->hp = hp;
- it->eom = (char *)hp + packsize;
- it->rr.rr_dname = it->dname;
- it->ancount = it->hp->ancount;
- it->nscount = it->hp->nscount;
- it->arcount = it->hp->arcount;
- qdcount = it->hp->qdcount;
- /* skip over questions */
- while (qdcount > 0) {
- dnamelen = dn_expand((char *)it->hp, it->eom, it->dp,
- it->dname, MAXDNAME);
- if (dnamelen < 0) {
- it->ancount = it->nscount = it->arcount = 0;
- }
- it->dp += dnamelen;
- it->dp += 4; /* skip over class and type */
- --qdcount;
- }
- }
-
-
- /*
- * find_a_records - look for an A record for the target
- *
- * Look for an A record for the target, and return an appropriate
- * response code:
- *
- * DB_SUCCEED An A record was found for the target.
- * DB_NOMATCH There was not an A record.
- * DB_FAIL There was a server error for this query.
- * DB_AGAIN There was a server error for this query, try
- * again later.
- * FILE_AGAIN Server error, try again later.
- * FILE_NOMATCH Could not connect to server.
- *
- * For response codes other than DB_SUCCEED and DB_NOMATCH, store an
- * error message.
- */
- static int
- find_a_records(mx_hint, target, error)
- struct mx_transport_hint * mx_hint;
- char *target;
- char **error;
- {
- int success;
- HEADER *a_rrs;
- int a_size;
- struct rr_iterator a_it;
- RR *a_rr;
- int result = DB_NOMATCH;
-
- a_rrs = (HEADER *)xmalloc(MAXPACKET);
- success = get_records(target, T_A, a_rrs, &a_size, error);
-
- if (success != DB_SUCCEED) {
- xfree((char *)a_rrs);
- return success;
- }
-
- rewindrr(&a_it, a_rrs, a_size);
- while ((a_rr = getnextrr(&a_it, FALSE))!=0) {
- if (a_rr->rr_type == T_A) {
- result = DB_SUCCEED;
- add_a_hint(mx_hint, a_it.dname, a_rr->rr_data);
- }
- }
- xfree((char *)a_rrs);
- return result;
- }
-
- static int
- decode_mx_rr(rr, it, name, precedence)
- RR *rr;
- struct rr_iterator *it;
- char **name;
- int *precedence;
- {
- static char nambuf[MAXDNAME];
- unsigned char *s = (unsigned char *)rr->rr_data;
- int dlen;
-
- GETSHORT(*precedence, s);
- dlen = dn_expand((char *)it->hp, it->eom, s, nambuf, MAXDNAME);
- if (dlen < 0) {
- return DB_FAIL;
- }
- *name = nambuf;
- return DB_SUCCEED;
- }
-
-
- /*
- * Create error structures for various errors.
- */
-
- static struct error *
- lost_server(what)
- char *what;
- {
- char *error_text;
-
- /*
- * ERR_163 - lost connection to BIND server
- *
- * DESCRIPTION
- * Lost connection to the nameserver.
- *
- * ACTIONS
- * Try again later.
- *
- * RESOLUTION
- * Hopefully, a later retry will reconnect.
- */
- error_text = xprintf("%s: Lost connection to BIND server: %s",
- what, strerrno());
- DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);
-
- return note_error(ERR_163, error_text);
- }
-
- static struct error *
- server_failure(what, error)
- char *what;
- char *error; /* additional error text */
- {
- char *error_text;
-
- /*
- * ERR_164 - failure talking to BIND server
- *
- * DESCRIPTION
- * An error occured when sending or receiving packets from
- * the BIND server, or the server registered an error in the
- * response packet.
- *
- * ACTIONS
- * Actions depend upon the specific error. Usually, a retry
- * is attempted later.
- *
- * RESOLUTION
- * Resolution depends upon the specific error.
- */
- error_text = xprintf("%s: BIND server failure: %s: %s",
- what, error, strerrno());
- DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);
-
- return note_error(ERR_NPOSTMAST | ERR_164, error_text);
- }
-
- static struct error *
- packet_error(what, type, host)
- char *what;
- char *type; /* type name of packet */
- char *host; /* target host */
- {
- char *error_text;
-
- /*
- * ERR_165 - response packet format error
- *
- * DESCRIPTION
- * A format error was found in a packet returned by the BIND
- * name server.
- *
- * ACTIONS
- * Retry again later, in the hope that the problem will go
- * away.
- *
- * RESOLUTION
- * This is probably a bug in the nameserver.
- */
- error_text =
- xprintf("%s: BIND server format error in %s packet for %s",
- what, type, host);
- DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);
-
- return note_error(ERR_165, error_text);
- }
-
- static struct error *
- no_valid_mx_records(what, host)
- char *what;
- char *host; /* target hostname */
- {
- char *error_text;
-
- /*
- * ERR_168 - no valid MX records for host
- *
- * DESCRIPTION
- * There were MX records for the target host, though all of
- * them were rejected.
- *
- * ACTIONS
- * Delivery to the target fails.
- *
- * RESOLUTION
- * The postmaster should look into the problem.
- */
- error_text = xprintf("%s: no valid MX records for %s",
- what, host);
- DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);
-
- return note_error(ERR_NSOWNER | ERR_168, error_text);
- }
-
- static struct error *
- matched_local_host(what, host)
- char *what;
- char *host; /* target hostname */
- {
- char *error_text;
-
- /*
- * ERR_169 - MX record points to local host
- *
- * DESCRIPTION
- * The MX record for the target host points to the local
- * host, but the local host is not prepared to handle this
- * case.
- *
- * ACTIONS
- * The domain database should probably be looked at.
- *
- * RESOLUTION
- * The postmaster should probably look into the problem.
- */
- error_text = xprintf("%s: MX record for %s points to local host",
- what, host);
- DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);
-
- return note_error(ERR_NSOWNER | ERR_169, error_text);
- }
-
- static struct error *
- no_transport_error(what, transport)
- char *what;
- char *transport; /* name of missing transport */
- {
- char *error_text;
-
- /*
- * ERR_110 - no transport for router
- *
- * DESCRIPTION
- * A format error was found in a packet returned by the BIND
- * name server.
- *
- * ACTIONS
- * Defer delivery.
- *
- * RESOLUTION
- * This is a configuration bug.
- */
- error_text =
- xprintf("%s: no such transport %s", what, transport);
- DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);
-
- return note_error(ERR_CONFERR | ERR_110, error_text);
- }
-
- static struct transport_hints *
- new_mx_hint(pref, dname, implicit)
- int pref;
- char * dname;
- int implicit;
- {
- struct transport_hints * new_hint;
- struct mx_transport_hint * mx_hint;
-
- new_hint = (struct transport_hints *) xmalloc(sizeof(*new_hint));
- mx_hint = (struct mx_transport_hint *) xmalloc(sizeof(*mx_hint));
- new_hint->succ = (struct transport_hints *) 0;
- new_hint->hint_name = "mx";
- new_hint->private = (char *) mx_hint;
- mx_hint->preference = pref;
- mx_hint->exchanger = dname;
- mx_hint->implicit = implicit;
- mx_hint->ipaddrs = (struct ipaddr_hint *) 0;
- return new_hint;
- }
-
- static void
- add_mx_hint(addr, precedence, name, implicit)
- struct transport_hints ** addr;
- int precedence;
- char * name;
- int implicit;
- {
- struct transport_hints * new_hint;
-
- new_hint = new_mx_hint(precedence, COPY_STRING(name), implicit);
- #define mxhint(hint) ((struct mx_transport_hint *)((hint)->private))
- while (*addr && (! EQ("mx",(*addr)->hint_name)
- || mxhint(*addr)->preference < precedence))
- {
- addr = &(*addr)->succ;
- }
- #undef mxhint
- new_hint->succ = *addr;
- *addr = new_hint;
- }
-
- static void
- add_a_hint(mx_hint, hostname, address)
- struct mx_transport_hint *mx_hint;
- char *hostname;
- char *address;
- {
- struct ipaddr_hint * a_hint, ** ah;
-
- a_hint = (struct ipaddr_hint *) xmalloc(sizeof(*a_hint));
- a_hint->succ = NULL;
- a_hint->hostname = COPY_STRING(hostname);
- memcpy(&a_hint->addr, address, sizeof(a_hint->addr));
-
- ah = &mx_hint->ipaddrs;
- while (*ah) {
- ah = &(*ah)->succ;
- }
- (*ah) = a_hint;
- }
-
- static void
- free_mx_hint(hint_p)
- struct transport_hints ** hint_p;
- {
- struct transport_hints * next = (*hint_p)->succ;
- struct ipaddr_hint * addr, * next_addr;
-
- #define mxhint ((struct mx_transport_hint *)((*hint_p)->private))
- for (addr = mxhint->ipaddrs; addr; addr = next_addr) {
- next_addr = addr->succ;
- xfree(addr->hostname);
- xfree((char *)addr);
- }
- xfree(mxhint->exchanger);
- xfree((char *)mxhint);
- #undef mxhint
- xfree((char *)*hint_p);
- *hint_p = next;
- }
-
- #endif /* HAVE_BIND */
-